/**
 * MOTOR 2 - RAPID FLASH DYNAMICS
 * Copyright (c) 2007-2008, Michael Baczynski
 * Based on the Box2D Engine by Erin Catto, http://www.box2d.org
 * 
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * * Neither the name of the polygonal nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without specific
 *   prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package de.polygonal.motor2.collision.shapes {
	import de.gameduell.framework.debug.Debug;	
	import de.polygonal.motor2.math.Tri2;	
	import de.polygonal.ds.BinaryTreeNode;
	import de.polygonal.motor2.collision.nbody.ShapeProxy;
	import de.polygonal.motor2.collision.shapes.ShapeTypes;
	import de.polygonal.motor2.collision.shapes.data.PolyData;
	import de.polygonal.motor2.dynamics.RigidBody;
	import de.polygonal.motor2.math.ConvexBSP;
	import de.polygonal.motor2.math.ConvexBSPNode;
	import de.polygonal.motor2.math.V2;	

	public class PolyShape extends ShapeSkeleton {
		/**
		 * OBB position & rotation in modeling-space -
		 * used internally for updating the aabb.
		 */
		private var _x:Number, _r11:Number, _r12:Number;
		private var _y:Number, _r21:Number, _r22:Number;

		/**
		 * cached OBB vertex chain
		 */
		private const _v0:V2 = new V2();
		private const _v1:V2 = new V2();
		private const _v2:V2 = new V2();
		private const _v3:V2 = new V2();

		private var _triCenter:V2;

		public function PolyShape(sd:PolyData, rb:RigidBody) {
			super(sd, rb);
			setup(sd, rb);
		}

		private function setup(sd:PolyData, rb:RigidBody):void {
			var xLocalCenter:Number, xt:Number, s:Number;
			var yLocalCenter:Number, yt:Number, c:Number;
			var verts:Array, modelVertexList:Array;
			var i:int, r:Number, v:V2, pos:V2, ext:V2;
			
			/* modeling space position */
			xLocalCenter = rb.cx;
			yLocalCenter = rb.cy;
			mx = sd.mx - xLocalCenter;
			my = sd.my - yLocalCenter;
			
			/* modeling space orientation */
			s = Math.sin(sd.mr);
			c = Math.cos(sd.mr);
			r11 = c; 
			r12 = -s;
			r21 = s; 
			r22 = c;
			
			/* sphere proxy */
			radius = sd.radius;
			radiusSq = radius * radius;
			
			/* setup polygon */
			vertexCount = sd.getVertexCount();
			
			modelVertexList = new Array(vertexCount);
			verts = sd.getVertices();
			for (i = 0;i < vertexCount; i++) {
				v = verts[i];
				xt = mx + r11 * v.x + r12 * v.y;
				yt = my + r21 * v.x + r22 * v.y;
				modelVertexList[i] = new V2(xt, yt);
			}
			initPoly(modelVertexList, vertexCount, sd.isRegular());
			
			/* setup minimum area rectangle */
			pos = new V2();
			ext = new V2();
			
			r = computeMinAreaRect(pos, ext);
			
			_x = pos.x; 
			ex = ext.x; 
			_y = pos.y; 
			ey = ext.y;
			s = Math.sin(r);
			c = Math.cos(r);
			_r11 = c; 
			_r12 = -s;
			_r21 = s; 
			_r22 = c;
			
			_v0.next = _v1;
			_v1.next = _v2;
			_v2.next = _v3;
			
			/* precompute bsp */
			BSPNode = ConvexBSP.createBSP(vertexCount, modelNormalChain.toArray(), edges);
			BinaryTreeNode.inorder(BSPNode, function(node:ConvexBSPNode):void {
				node.N = worldNormalChain.getAt(node.I);
				node.V = worldVertexChain.getAt(node.I);
			});
				
			createProxy(sd.groupIndex, sd.categoryBits, sd.maskBits);
		}

		override public function update():void {
			synced = false;
			
			/* WCS transform */
			x = body.x; 
			r11 = body.r11; 
			r12 = body.r12;
			y = body.y; 
			r21 = body.r21; 
			r22 = body.r22;
			
			/* refit MBR to OBB */
			xmin = xmax = x + _x;
			ymin = ymax = y + _y;
			var t:Number = _r11 * r11 + _r12 * r21;
			if (t > 0) {
				xmin += t * -ex;
				xmax += t * ex;
			}
			else {
				xmin += t * ex;
				xmax += t * -ex;
			}
			
			t = _r11 * r12 + _r12 * r22;
			if (t > 0) {
				xmin += t * -ey;
				xmax += t * ey;
			}
			else {
				xmin += t * ey;
				xmax += t * -ey;
			}
			
			t = _r21 * r11 + _r22 * r21;
			if (t > 0) {
				ymin += t * -ex;
				ymax += t * ex;
			}
			else {
				ymin += t * ex;
				ymax += t * -ex;
			}
			
			t = _r21 * r12 + _r22 * r22;
			if (t > 0) {
				ymin += t * -ey;
				ymax += t * ey;
			}
			else {
				ymin += t * ey;
				ymax += t * -ey;
			}
			
			/* update triangle's center */
			if (_triCenter) {
				_triCenter.x = x;
				_triCenter.y = y;
			}
			
			if (proxyId == ShapeProxy.NULL_PROXY) return;
			
			if (!_broadPhase.insideBounds(xmin, ymin, xmax, ymax)) {
				_broadPhase.removeProxy(proxyId);
				proxyId = ShapeProxy.NULL_PROXY;
				body.freeze();
			}
		}

		override public function toWorldSpace():void {
			var wv:V2 = worldVertexChain;
			var mv:V2 = modelVertexChain;
			
			var wn:V2 = worldNormalChain;
			var mn:V2 = modelNormalChain;
			
			while (true) {
				wv.x = r11 * mv.x + r12 * mv.y + x;
				wv.y = r21 * mv.x + r22 * mv.y + y;
				
				wn.x = r11 * mn.x + r12 * mn.y;
				wn.y = r21 * mn.x + r22 * mn.y;
				
				if (wv.last) break;
				
				wv = wv.next;
				mv = mv.next;
				
				wn = wn.next;
				mn = mn.next;
			}
		}

		override public function closestPoint(x:Number, y:Number, out:V2):void {
			var ex:Number, dx:Number, tx:Number, px:Number; 
			var ey:Number, dy:Number, ty:Number, py:Number;
			
			var minSq:Number = -1, dSq:Number, interp:Number;
			var v0:V2, v1:V2;
			
			//search is done in modeling space
			px = r11 * (x - this.x) + r21 * (y - this.y);
			py = r12 * (x - this.x) + r22 * (y - this.y);
			
			v0 = modelVertexChain;
			v1 = v0.next;
			while (true) {
				ex = v1.x - v0.x; 
				ey = v1.y - v0.y;
				
				dx = px - v0.x;
				dy = py - v0.y;
				
				if (dx * ex + dy * ey > 0) {
					interp = (dx * ex + dy * ey) / (ex * ex + ey * ey);
					interp = (interp < 0) ? 0 : (interp > 1) ? 1 : interp;
					
					tx = v0.x + ex * interp;
					ty = v0.y + ey * interp;
					
					dSq = (px - tx) * (px - tx) + (py - ty) * (py - ty);
					if (dSq < minSq || minSq < 0) {
						minSq = dSq;
						out.x = this.x + r11 * tx + r12 * ty;
						out.y = this.y + r21 * tx + r22 * ty;
					}
				}
				
				if (v0.last) break;
				v0 = v1;
				v1 = v1.next;
			}
		}

		override public function pointInside(x:Number, y:Number):Boolean {
			//search is done in modeling space
			var px:Number = r11 * (x - this.x) + r21 * (y - this.y);
			var py:Number = r12 * (x - this.x) + r22 * (y - this.y);
			var v:V2 = modelVertexChain;
			var n:V2 = modelNormalChain;
			while (v) {
				if ((px - v.x) * n.x + (py - v.y) * n.y > 0)
					return false;
				
				if (v.last) break;
				n = n.next;
				v = v.next;
			}
			return true;
		}

		public function getWorldOBB():V2 {
			var wx:Number = _r11 * ex;
			var wy:Number = _r21 * ex;
			var hx:Number = _r12 * ey;
			var hy:Number = _r22 * ey;
			_v0.x = _x + wx - hx;
			_v0.y = _y + wy - hy;
			
			_v1.x = _x - wx - hx;
			_v1.y = _y - wy - hy;
			
			_v2.x = _x - wx + hx;
			_v2.y = _y - wy + hy;
			
			_v3.x = _x + wx + hx;
			_v3.y = _y + wy + hy;
			return _v0;
		}

		override public function triangulate():void {
			//triangulate polygon by center-point
			_triCenter = new V2(x, y);
			var v:V2 = worldVertexChain;
			while (true) {
				var t:Tri2 = new Tri2(v, v.next, _triCenter);
				t.next = triangleList;
				triangleList = t;
				
				if (v.last) break; 
				v = v.next;
			}
		}

		override protected function setType():void {
			type = ShapeTypes.POLY;
		}
	}
}